Skip to content

fix(select): Select component keyboard type-ahead#10006

Open
withlakshmanan wants to merge 8 commits into
adobe:mainfrom
withlakshmanan:fix/select-type-a-head-issue
Open

fix(select): Select component keyboard type-ahead#10006
withlakshmanan wants to merge 8 commits into
adobe:mainfrom
withlakshmanan:fix/select-type-a-head-issue

Conversation

@withlakshmanan
Copy link
Copy Markdown

@withlakshmanan withlakshmanan commented May 2, 2026

Closes #8919

Description
The fix makes Select keyboard typeahead behave correctly for repeated letters and ensures search continues from the next option rather than reusing the current focused item.

Screen recording

Screen.Recording.2026-04-25.at.1.58.40.PM.mov

Issue link
#8919

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices

📝 Test Instructions:

🧢 Your Project:

@github-actions github-actions Bot added the RAC label May 2, 2026
@snowystinger
Copy link
Copy Markdown
Member

Looks like it's still failing tests, how would you like to handle those? Do you need assistance? Also, it looks like you haven't signed the CLA, see https://github.com/adobe/react-spectrum/blob/main/CONTRIBUTING.md#contributor-license-agreement

@withlakshmanan
Copy link
Copy Markdown
Author

@snowystinger I already signed though the email notification. but still not passing. I need your help on that.

@snowystinger
Copy link
Copy Markdown
Member

I've updated with a merge from main, hopefully that fixes the remaining two test failures that I don't think were your fault.

As for the CLA, could you try again and double check the e-mail used. This happens pretty frequently, especially when people have multiple accounts/emails/etc. It doesn't look like the service is down as other PRs are being successfully checked.

@withlakshmanan
Copy link
Copy Markdown
Author

Passed all the quality gate issues @snowystinger. please review this PR

@withlakshmanan withlakshmanan force-pushed the fix/select-type-a-head-issue branch from c20a67e to f5fe02e Compare May 8, 2026 06:19
@withlakshmanan
Copy link
Copy Markdown
Author

withlakshmanan commented May 12, 2026

@reidbarber @snowystinger @devongovett Please review this PR. I fixed all the quality gate issues. some check fails due to reverse merge main branch.

@snowystinger
Copy link
Copy Markdown
Member

Thanks for being patient, we are looking for some time to review.

Copy link
Copy Markdown
Member

@snowystinger snowystinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your patience

Looks like we want it to match the behaviour of native #8919 (comment)

I think we have some tests that are currently wrong, so we should update the behaviour.

expect(document.activeElement).toBe(options[0]);

I think this should be on option[5] after pressing F from the first item since that was already Foo.

Here's the implementation of Chrome's https://github.com/chromium/chromium/blob/main/third_party/blink/renderer/core/html/forms/type_ahead.cc
We can probably ignore their part about the time delta, we already implement that with the timer. And we can defer text value matching to the delegates.

await user.tab();
await user.keyboard('bb');
expect(trigger).toHaveTextContent('Blackberry');
});
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    it('should wrap if typeahead is not found after the current key', async function () {
      let {getByTestId} = render(
        <Select defaultSelectedKey="blueberry" data-testid="select">
          <Label>Favorite Fruit</Label>
          <Button>
            <SelectValue />
          </Button>
          <Popover>
            <ListBox>
              <ListBoxItem id="banana">Banana</ListBoxItem>
              <ListBoxItem id="blackberry">Blackberry</ListBoxItem>
              <ListBoxItem id="blueberry">Blueberry</ListBoxItem>
            </ListBox>
          </Popover>
        </Select>
      );

      let wrapper = getByTestId('select');
      let selectTester = testUtilUser.createTester('Select', {
        root: wrapper,
        interactionType: 'keyboard'
      });
      let trigger = selectTester.getTrigger();

      await user.tab();
      await user.keyboard('b');
      expect(trigger).toHaveTextContent('Banana');
    });

    // This matches the three main browsers behavior. Even though it seems like it should go to double "b" blackberry,
    // it's just cycling through the items that start with "b".
    it('searches the next item that starts with the same letter, not the next item that starts with the same letter twice', async function () {
      let {getByTestId} = render(
        <Select data-testid="select">
          <Label>Favorite Fruit</Label>
          <Button>
            <SelectValue />
          </Button>
          <Popover>
            <ListBox>
              <ListBoxItem id="banana">Banana</ListBoxItem>
              <ListBoxItem id="boisenberry">Boisenberry</ListBoxItem>
              <ListBoxItem id="bblackberry">Bblackberry</ListBoxItem>
              <ListBoxItem id="blueberry">Blueberry</ListBoxItem>
            </ListBox>
          </Popover>
        </Select>
      );

      let wrapper = getByTestId('select');
      let selectTester = testUtilUser.createTester('Select', {
        root: wrapper,
        interactionType: 'keyboard'
      });
      let trigger = selectTester.getTrigger();

      await user.tab();
      await user.keyboard('bb');
      expect(trigger).toHaveTextContent('Boisenberry');
    });


key = this.getNextKey(key);

if (key == null && !hasWrapped) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't need to do wrapping here, i think we already handle it in useTypeSelection, if a key wasn't found after the starting point, we try again from the top of the list


let collection = this.collection;
let key = fromKey || this.getFirstKey();
let key = fromKey != null ? this.getNextKey(fromKey) : this.getFirstKey();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be better if we can handle this in useTypeSelect instead, it may affect unrelated implementations, and we'd need to go make the change in every delegate and layout which implements this function probably

if (
!character ||
e.ctrlKey ||
e.metaKey ||
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like we're missing an e.altKey || check according to the chrome implementation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RAC Select Type-ahead not working as per ARIA guidelines

2 participants